/**
 *  modlist.c -  crea una entrada /proc/modlist y permite la gestion de
 *  una lista de enteros
 *  http://www.cs.bham.ac.uk/~exr/teaching/lectures/systems/08_09/docs/kernelAPI/x1820.html
 *  http://www.roman10.net/linux-kernel-programmingmemory-allocation/
 *  http://www.makelinux.net/ldd3/chp-11-sect-5
 *  http://stackoverflow.com/questions/15332381/linux-list-h-how-to-safely-remove-items-from-the-list
 *  http://stackoverflow.com/questions/14174650/how-to-delete-a-linked-list-in-c
 */

#include <linux/module.h>	/* Specifically, a module */
#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/proc_fs.h>	/* Necessary because we use the proc fs */
#include <asm/uaccess.h>	/* for copy_from_user */
#include <linux/vmalloc.h>  /* memoria */
#include <linux/list.h>		/* para listas */
#include <linux/kthread.h>  /* kernel threads */

#define PROCFS_MAX_SIZE		512
#define MAX_WORDS 10
#define PROCFS_NAME 		"mmodlist"
#define PROCFS_NAME_LISTA 		"lista"

/** Necesitamos:
 * 1. Gestionar los argumentos de entrada -> reconocer add / remove /cleanup (100%)
 * 2. Gestionar la lista de enteros -> lista enlazada con head_list (100%)
 * 3. Actuar con la lista segus los argumentos que entren (EN PROCESO -> 50% -> FALLOS DETECTADOS)
 */
 
MODULE_LICENSE("GPL");

MODULE_DESCRIPTION("Practica 2a AISO");

MODULE_AUTHOR("Raul Vicente Bueno Sevilla - Jesus Francisco Lopez San Jose");

static int NLIST = 1; // numero de listas maximo

static struct task_struct *ts; // estructura de un kernel thread
static int activo;
static int periodo = 0;				// periodo en el que se escribe en el log del kernel
static int mostrar = 0;

LIST_HEAD(miLista);    // inicializamos la lista-> LIST_HEAD(&miLista); 
static int numList = 0; // numero de listas
static int listaActual = 0; //variable para indicar la lista con la que trabajamos

module_param(NLIST, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(NLIST, "numero de listas que se crean");

/************* Definicion elementos de la lista **********************************/
/** Nodos de la lista - DEF STRUCT */
struct Node{
	int dato;
    struct list_head list; /* estructura de lista de kernel */
};

struct List{
	int id;
	int numElems;
	struct list_head list_elems; /*lista actual*/
	struct list_head list; /*punteros a otras listas */
}; 

/************ definicion procs entry *************************/

/**
 * This structure hold information about the /proc file
 *
 */
static struct proc_dir_entry *Our_Proc_File;

/**
 * This structure hold information about the /proc/lista file
 * and others
 */
static struct proc_dir_entry *Proc_lista;
static struct proc_dir_entry *Proc_seleccion;
static struct proc_dir_entry *Proc_periodo;

/**
 * The buffer used to store character for this module
 *
 */
static char procfs_buffer[PROCFS_MAX_SIZE];

/**
 * The size of the buffer
 *
 */
static unsigned long procfs_buffer_size = 0;
static int long_substring = 0;

/**************** FUNCIONES COMPORTAMIENTO THREAD **************************/

int showListNumElems(int idList){
	struct List* pList;
	
	/* buscamos la lista */
	list_for_each_entry(pList, &miLista, list){	
		if (pList->id == idList){			
			printk(KERN_ALERT "Lista actual: %i. Num elementos: %i\n",idList, pList->numElems); /** DEBUG */ 
		}

	}
	return 0;
}

int mostrarInfo(void *nousado) {
  allow_signal(SIGKILL);
  //printk(KERN_INFO "THEjemplo: iniciando ejecuacion kernel thread ejemplo.\n");
  activo = 1;	  
  for(;;){
	msleep(100);
	// cada vez que se cambie de lista o se modifique, hay que mostrar la informacion
    if (periodo == 0 && mostrar == 1){
		//muestra la informacion
		showListNumElems(listaActual);
		mostrar = 0;
	}
	else if (periodo != 0){
		msleep(10000);
		showListNumElems(listaActual);
	}
    if (signal_pending(current)){
      activo = 0; 
      break;
      }
    if (kthread_should_stop()) {
      activo = 0;
      break;
    }
  }
  return 0;
}

/**************** FUNCIONES AUX PARA TRATAR LA LISTA ***********************/
/** añade una lista a la lista de listas */
int addList(){
	struct List *pNewList;

	if (numList >= NLIST){ /* no se pueden añadir mas listas */
		printk(KERN_ALERT "No se ha podido crear la lista"); /** DEBUG */
		return 1;
	}		
	
	pNewList = vmalloc(sizeof(pNewList)); //reserva de memoria para el nuevo nodo
	pNewList->numElems = 0;
	pNewList->id = numList;
	INIT_LIST_HEAD(&pNewList->list);
	INIT_LIST_HEAD(&pNewList->list_elems); //inicializamos la lista de elementos
	list_add_tail(&pNewList->list,&miLista);
	numList = numList + 1;
	
	printk(KERN_ALERT "Lista con id %i creada\n",pNewList->id); /** DEBUG */
	return 0;
}

/** elimina una lista de la lista de listas */
int removeList(int idList){
	
	struct List *pList, *tempList;
	
	//list_for_each_entry_safe(type *cursor, type *next, struct list_head *list, member)
	list_for_each_entry_safe(pList, tempList, &miLista, list){
		if (pList->id == idList){
			list_del(&pList->list);
			vfree(pList);
			numList = numList - 1;
			printk(KERN_ALERT "Lista con id %i eliminada\n",idList); /** DEBUG */
		}
		
	}
	return 0;
}


/**
 *	Incluye un elemento nuevo a la lista
 */
int addElement(int n,int idList){
	struct List *pList;
	struct Node *pNewNodo;
	
	/* tenemos que encontrar la lista indicada */
	list_for_each_entry(pList, &miLista, list){
		// si encontramos la id de la lista
		if (pList->id == idList){
			pNewNodo = vmalloc(sizeof(pNewNodo)); //reserva de memoria para el nuevo nodo
			pNewNodo->dato=n;
			//incluimos el nuevo elemento a la lista
			INIT_LIST_HEAD(&pNewNodo->list);
			list_add_tail(&pNewNodo->list,&pList->list_elems);
			pList->numElems = pList->numElems+1;
			printk(KERN_ALERT "Metido nodo %i en lista %i \n",n,idList); /** DEBUG */
		}
		
	}	
	return 0;
}

/**
 * muestra los elementos de la lista usando printk 
 */
int showList(int idList){
	struct Node *pNodo;
	struct List *pList;
	
	/* http://www.makelinux.net/ldd3/chp-11-sect-5
	 * list_for_each_entry(type *cursor, struct list_head *list, member)
	 * cursor is a pointer to the containing structure type
	 * member is the name of the list_head structure
	 */
	printk(KERN_ALERT "Mostrar lista %i\n",idList); /** DEBUG */
	/* buscamos la lista */
	list_for_each_entry(pList, &miLista, list){	
		if (pList->id == idList){
		
			list_for_each_entry(pNodo, &pList->list_elems, list){
				printk(KERN_ALERT "[%i]", pNodo->dato); 
			}
			
			printk(KERN_ALERT "Num El: %i\n",pList->numElems); /** DEBUG */ 
		}

	}
	
	return 0;
}

/**
 *	Borra todos los nodos que tengan un numero n. Devuelve el num de nodos cuyo int sea n y se hayan borrado
 */
int removeElement(int n, int idList){
	struct List *pList;
	struct Node *pNodo, *tempNodo;
	int elemsBorrar = 0;
	
	/* buscamos la lista indicada */
	list_for_each_entry(pList, &miLista, list){
		
		if (pList->id == idList){ // si encontramos la id de la lista
			
			/*list_for_each_entry_safe(type *cursor, type *next, struct list_head *list, member) */
			list_for_each_entry_safe(pNodo, tempNodo, &pList->list_elems, list){
				/* buscamos los nodos */
				if (pNodo->dato == n){
					list_del(&pNodo->list);
					vfree(pNodo);
					elemsBorrar = elemsBorrar + 1;
				}
			
				/* Una vez actualizado el acumulador de los elementos que borramos, lo descontamos
				* del numero de elementos de la lista */	
			}
			
			pList->numElems = pList->numElems - elemsBorrar;
			printk(KERN_ALERT "Se han eliminado %i nodos de la lista %i\n",elemsBorrar,idList); /**DEBUG*/
		}

	}
	
	return 0;
}


/**
 *	Elimina todos los nodos de la lista
 */
int removeAllElements(int idList){
	struct List *pList;
	struct Node *pNodo;
	
	list_for_each_entry(pList, &miLista, list){
		
		if (pList->id == idList){ // si encontramos la id de la lista
			/* borramos los elementos de esa lista */
			while (pList->numElems > 0){
				pNodo = list_first_entry(&pList->list_elems, struct Node, list);
				list_del(&pNodo->list);
				vfree(pNodo);
				pList->numElems = pList->numElems - 1;
			}
			printk(KERN_ALERT "Se han eliminado todos los elementos de la lista %i", pList->id); /** DEBUG */	
		}
	}

	return 0;
}

/** para borrar el contenido de cada lista y luego la lista */
int removeAll(){
	while (numList > 0){
		removeAllElements(numList - 1);
		removeList(numList - 1);
		printk(KERN_ALERT "Quedan %i listas\n",numList); /** DEBUG */
	}
	return 0;
}

/************************ Funciones de read y write de los proc *************************/
/** 
 * This function is called then the /proc/lista file is read
 *
 */
int 
procfile_read_lista(char *buffer,
	      char **buffer_location,
	      off_t offset, int buffer_length, int *eof, void *data)
{
	int ret;
	
	printk(KERN_ALERT "procfile_read (/proc/%s) called\n", PROCFS_NAME); /** DEBUG */
	showList(listaActual);
	ret = 0; //finished to read
	
	return ret;
}

/** funcion que se llama cuando se escribe en seleccion */
int procfile_write_seleccion(struct file *file, const char *buffer, unsigned long count,
		   void *data)
{	
	char *words[MAX_WORDS], *p, *val;
	/* buffer de char para guardar*/
	char buf[procfs_buffer_size];
	int i = 0;
	long res;
	char** pEnd;
	
	procfs_buffer_size = count;
	
	printk(KERN_ALERT "procfile_write (/proc/mmodlist/%s) called\n", "seleccion");
	if (procfs_buffer_size > PROCFS_MAX_SIZE ) {
		procfs_buffer_size = PROCFS_MAX_SIZE;
	}
	copy_from_user(procfs_buffer, buffer, procfs_buffer_size); //copiamos del usuario (buffer)  a espacio del kernel (procfs_buffer)
	
	sprintf(buf,procfs_buffer);
	
	// nos quedamos con el primer argumento, que debe ser un numero
	for (p = buf; p != NULL;){
		while ((val = strsep(&p, " \t")) != NULL && *val == '\0');
		if (i < MAX_WORDS){		
			words[i] = val;		
			i++;
		}
	}
	//printk(KERN_ALERT "lista seleccionada %s",words[0]);
	
	/*Lo convertimos a integer con simple_strtol() (2.6.20 kernel)*/
	res = simple_strtol(words[0],pEnd,10);
	
	// ahora ajustamos
	if (res >= 0 && res < NLIST){
		listaActual = res;
		mostrar = 1;
		//printk(KERN_ALERT "lista seleccionada: %i",listaActual);
	}
	else
		printk(KERN_ALERT "No se ha podido seleccionar una nueva lista");		
		//printk(KERN_ALERT "No se ha podido seleccionar una nueva lista. La actual es: %i",listaActual);
		
	return procfs_buffer_size;
	
}

/** funcion que se llama cuando se escribe en seleccion */
int procfile_write_periodo(struct file *file, const char *buffer, unsigned long count,
		   void *data)
{	
	char *words[MAX_WORDS], *p, *val;
	/* buffer de char para guardar*/
	char buf[procfs_buffer_size];
	int i = 0;
	long res;
	char** pEnd;
	
	procfs_buffer_size = count;
	
	printk(KERN_ALERT "procfile_write (/proc/mmodlist/%s) called\n", "periodo");
	if (procfs_buffer_size > PROCFS_MAX_SIZE ) {
		procfs_buffer_size = PROCFS_MAX_SIZE;
	}
	copy_from_user(procfs_buffer, buffer, procfs_buffer_size); //copiamos del usuario (buffer)  a espacio del kernel (procfs_buffer)
	
	sprintf(buf,procfs_buffer);
	
	// nos quedamos con el primer argumento, que debe ser un numero
	for (p = buf; p != NULL;){
		while ((val = strsep(&p, " \t")) != NULL && *val == '\0');
		if (i < MAX_WORDS){		
			words[i] = val;		
			i++;
		}
	}
	//printk(KERN_ALERT "lista seleccionada %s",words[0]);
	
	/*Lo convertimos a integer con simple_strtol() (2.6.20 kernel)*/
	res = simple_strtol(words[0],pEnd,10);
	periodo = res;
		
	return procfs_buffer_size;
	
}

/**
 * This function is called with the /proc/lista file is written
 *
 */
int procfile_write_lista(struct file *file, const char *buffer, unsigned long count,
		   void *data)
{	
	char *words[MAX_WORDS], *p, *val;
	/* buffer de char para guardar*/
	char buf[procfs_buffer_size];
	int i = 0;
	long res;
	char** pEnd;

	
	printk(KERN_ALERT "procfile_write (/proc/mmodlist%s) called\n", PROCFS_NAME_LISTA);
	/* get buffer size */
	procfs_buffer_size = count;
	

	if (procfs_buffer_size > PROCFS_MAX_SIZE ) {
		procfs_buffer_size = PROCFS_MAX_SIZE;
	}

	/* write data to the buffer */
	copy_from_user(procfs_buffer, buffer, procfs_buffer_size); //copiamos del usuario (buffer)  a espacio del kernel (procfs_buffer)
	sprintf(buf,procfs_buffer);
	
	for (p = buf; p != NULL;){
		while ((val = strsep(&p, " \t")) != NULL && *val == '\0');
		if (i < MAX_WORDS){		
			words[i] = val;		
			i++;
		}
	}

	if (strncmp(words[0],"add",3)==0){
		/*Lo convertimos a integer con simple_strtol() (2.6.20 kernel)*/
		res = simple_strtol(words[1],pEnd,10);
		addElement(res,listaActual); //incluimos un elemento en la lista
		mostrar = 1;
	}
	else if (strncmp(words[0],"remove",6)==0){
		res = simple_strtol(words[1],pEnd,10);
		removeElement(res,listaActual);
		mostrar = 1;
	}
	else if (strncmp(words[0],"cleanup",7 )==0){
		removeAllElements(listaActual);
		mostrar = 1;
	}
	
	return procfs_buffer_size;
}

/** la entrada proc/mmodlist/lista tendra las siguientes operaciones */
static struct file_operations fops_lista = {
    .owner = THIS_MODULE,
    .read = procfile_read_lista,
    .write = procfile_write_lista,
};

/** la entrada proc/mmodlist/seleccion tendra las siguientes operaciones */
static struct file_operations fops_seleccion = {
    .owner = THIS_MODULE,
    .write = procfile_write_seleccion,
};

/** la entrada proc/mmodlist/periodo tendra las siguientes operaciones */
static struct file_operations fops_periodo = {
    .owner = THIS_MODULE,
    .write = procfile_write_periodo,
};

/**********************FUNCIONES AXULIARES DE GESTION DEL PROC****************/
int init_proc_lista(){

	Proc_lista = proc_create(PROCFS_NAME_LISTA, 0644, Our_Proc_File , &fops_lista);
		//Proc_lista proc_mkdir( const char *name,struct proc_dir_entry *parent );
	if (Proc_lista == NULL) {
		//remove_proc_entry(PROCFS_NAME, &proc_root);
		remove_proc_entry(PROCFS_NAME_LISTA, NULL);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
			PROCFS_NAME_LISTA);
		return -ENOMEM;
	}
	
	Proc_lista->mode 	  = S_IFREG | S_IRUGO;
	Proc_lista->uid 	  = 0;
	Proc_lista->gid 	  = 0;
	Proc_lista->size 	  = 37;

	printk(KERN_ALERT "/proc/%s created\n", PROCFS_NAME_LISTA);
	
	while (numList < NLIST)
		addList(); /* creamos todas las listas que nos solicitan */
}

int init_proc_seleccion(){
		Proc_seleccion =  proc_create("seleccion", 0644, Our_Proc_File , &fops_seleccion);
		
		if (Proc_seleccion == NULL) {
		//remove_proc_entry(PROCFS_NAME, &proc_root);
		remove_proc_entry("seleccion", Our_Proc_File);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
			PROCFS_NAME);
		return -ENOMEM;
	}

	printk(KERN_ALERT "/proc/mmodlist/%s created\n", "seleccion");
	
	return 0;
}

int init_proc_periodo(){
		Proc_periodo =  proc_create("periodo", 0644, Our_Proc_File , &fops_periodo);
		
		if (Proc_periodo == NULL) {
		//remove_proc_entry(PROCFS_NAME, &proc_root);
		remove_proc_entry("periodo", Our_Proc_File);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s/%s\n",
			PROCFS_NAME,"periodo");
		return -ENOMEM;
	}

	printk(KERN_ALERT "/proc/mmodlist/%s created\n", "periodo");
	
	return 0;
}

/************************ FUNCIONES INIT Y EXIT DEL MODULO ****************************/
/**
 *This function is called when the module is loaded
 *
 */
int init_module()
{
	/* create the /proc file */
	//Our_Proc_File = create_proc_entry(PROCFS_NAME, 0644, NULL);
	Our_Proc_File = proc_mkdir(PROCFS_NAME,NULL);
	
	if (Our_Proc_File == NULL) {
		//remove_proc_entry(PROCFS_NAME, &proc_root);
		remove_proc_entry(PROCFS_NAME, NULL);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
			PROCFS_NAME);
		return -ENOMEM;
	}

	printk(KERN_ALERT "/proc/%s created\n", PROCFS_NAME);
	
	init_proc_lista();
	init_proc_seleccion();
	init_proc_periodo();
	ts=kthread_run(mostrarInfo,NULL,"mostrarInfo");

	return 0;	/* everything is ok */
}

/**
 *This function is called when the module is unloaded
 *
 */
void cleanup_module()
{
	//remove_proc_entry(PROCFS_NAME, &proc_root);
	removeAll();
	remove_proc_entry(PROCFS_NAME_LISTA,Our_Proc_File);
	remove_proc_entry(PROCFS_NAME, NULL);
	remove_proc_entry("seleccion", Our_Proc_File);
	remove_proc_entry("periodo", Our_Proc_File);
	if (activo)
		kthread_stop(ts);
	printk(KERN_ALERT "/proc/%s removed\n", PROCFS_NAME);
	printk(KERN_ALERT "/proc/%s/%s removed\n", PROCFS_NAME,PROCFS_NAME_LISTA);
	printk(KERN_ALERT "/proc/%s/%s removed\n",PROCFS_NAME, "seleccion");
	printk(KERN_ALERT "/proc/%s/%s removed\n",PROCFS_NAME, "periodo");
}
